#ifndef LINKEDLIST_H
#define LINKEDLIST_H

#include "node.h"

#include <utility>

// Do not add any #include statements here.  If you have a convincing need for
// adding a different `#include` please post in the forum on KEATS.

// your code goes here:
template <typename T>
class LinkedList
{
private:
    Node<T> *head;
    Node<T> *tail;
    size_t m_size;

public:
    LinkedList() : head(nullptr), tail(nullptr), m_size(0) {}

    // initializer list constructor
    LinkedList(std::initializer_list<T> init)
        : head(nullptr), tail(nullptr), m_size(0)
    {
        for (auto &item : init)
        {
            push_back(item);
        }
    }

    ~LinkedList()
    {
        Node<T> *curr = head;
        while (curr != nullptr)
        {
            Node<T> *next = curr->next;
            delete curr;
            curr = next;
        }
    }

    void push_front(const T &data)
    {
        Node<T> *newNode = new Node<T>(data);
        newNode->next = head;
        newNode->previous = nullptr;
        if (tail == nullptr)
        {
            tail = newNode;
        }
        else
        {
            head->previous = newNode;
        }
        head = newNode;
        ++m_size;
    }

    void push_back(const T &data)
    {
        Node<T> *newNode = new Node<T>(data);
        newNode->next = nullptr;
        newNode->previous = tail;
        if (head == nullptr)
        {
            head = newNode;
        }
        else
        {
            tail->next = newNode;
        }
        tail = newNode;
        ++m_size;
    }

    T &front() { return head->data; }

    const T &front() const { return head->data; }

    T &back() { return tail->data; }

    const T &back() const { return tail->data; }

    size_t size() const { return m_size; }

    void insert(Node<T> *pos, const T &data)
    {
        Node<T> *newNode = new Node<T>(data);
        newNode->next = pos;
        newNode->previous = pos->previous;
        if (pos == head)
        {
            head = newNode;
        }
        else
        {
            Node<T> *curr = head;
            while (curr != nullptr && curr->next != pos)
            {
                curr = curr->next;
            }
            if (curr != nullptr)
            {
                curr->next = newNode;
            }
        }
        ++m_size;
    }

    NodeIterator<T> begin() const { return NodeIterator<T>(head); }

    NodeIterator<T> end() const { return NodeIterator<T>(nullptr); }

    void reverse()
    {
        Node<T> *curr = head;
        while (curr != nullptr)
        {
            Node<T> *next = curr->next;
            curr->next = curr->previous;
            curr->previous = next;
            curr = next;
        }
        Node<T> *temp = head;
        head = tail;
        tail = temp;
    }

    NodeIterator<T> insert(NodeIterator<T> pos, const T &data)
    {
        Node<T> *newNode = new Node<T>(data);
        newNode->next = pos.operator->();
        if (pos.operator->() == head)
        {
            head = newNode;
        }
        else
        {
            Node<T> *curr = head;
            while (curr != nullptr && curr->next != pos.operator->())
            {
                curr = curr->next;
            }
            if (curr != nullptr)
            {
                curr->next = newNode;
            }
        }
        ++m_size;
        return NodeIterator<T>(newNode);
    }

    NodeIterator<T> erase(NodeIterator<T> pos)
    {
        Node<T> *curr = head;
        while (curr != nullptr && curr->next != pos.operator->())
        {
            curr = curr->next;
        }
        if (curr != nullptr)
        {
            curr->next = pos.operator->()->next;
            if (curr->next == nullptr)
            {
                tail = curr;
            }
        }
        NodeIterator<T> next(pos.operator->()->next);
        delete pos.operator->();
        --m_size;
        return next;
    }
};

// do not edit below this line

#endif
